package dk.mirasola.systemtraining.bridgewidgets.server.util;


import dk.mirasola.systemtraining.bridgewidgets.shared.model.Bid;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.Card;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.ContractBid;
import dk.mirasola.systemtraining.bridgewidgets.shared.model.SpecialBid;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class SerializationHelper {
    public static byte[] serializeCards(List<Card> cards) {
        if (cards == null) {
            throw new NullPointerException();
        }

        if (cards.size() > 255) {
            throw new IllegalArgumentException("Cannot serialize more than 63 cards");
        }

        byte[] result = new byte[cards.size() + 1];
        // encode number of cards in first byte
        result[0] = (byte) cards.size();

        int byteIndex = 0;
        int bitCount = 0;
        int buffer = 0;
        for (Card card : cards) {
            buffer |= card.getId() << bitCount;
            bitCount += 6;
            if (bitCount >= 8) {
                byteIndex++;
                result[byteIndex] = (byte) buffer;
                buffer = buffer >>> 8;
                bitCount -= 8;
            }
        }

        if (bitCount > 0) {
            byteIndex++;
            result[byteIndex] = (byte) buffer;
        }

        return Arrays.copyOf(result, byteIndex + 1);
    }

    public static List<Card> deserializeCards(byte[] bytes) {
        int cardsCount = bytes[0] & 0xFF;
        List<Card> result = new ArrayList<Card>(cardsCount);
        if (cardsCount == 0) {
            return result;
        }

        int buffer = 0;
        int bitCount = 0;
        for (int i = 1; i < bytes.length; i++) {
            buffer |= (bytes[i] & 0xFF) << bitCount;
            bitCount += 8;

            while (bitCount >= 6) {
                result.add(Card.get(buffer & 0x3F));
                buffer = buffer >>> 6;
                bitCount -= 6;

                if (result.size() == cardsCount) {
                    return result;
                }
            }
        }
        return result;
    }

    public static byte[] serializeBids(List<Bid> bids) {
        if (bids == null) {
            throw new NullPointerException();
        }

        if (bids.size() > 255) {
            throw new IllegalArgumentException("Cannot serialize more than 256 bids");
        }

        byte[] result = new byte[bids.size() + 1];
        // encode size in first byte
        result[0] = (byte) bids.size();

        int byteIndex = 0;
        int bitCount = 0;
        int buffer = 0;
        for (Bid bid : bids) {
            if (bid instanceof SpecialBid) {
                // 3 bit encoding <bidId>0
                buffer |= bid.getId() << bitCount + 1;
                bitCount += 3;
            } else {
                // 7 bit encoding <bidId>1
                buffer |= ((bid.getId() << 1) | 1) << bitCount;
                bitCount += 7;
            }

            if (bitCount >= 8) {
                byteIndex++;
                result[byteIndex] = (byte) buffer;
                buffer = buffer >>> 8;
                bitCount -= 8;
            }
        }

        if (bitCount > 0) {
            byteIndex++;
            result[byteIndex] = (byte) buffer;
        }

        return Arrays.copyOf(result, byteIndex + 1);
    }

    public static List<Bid> deserializeBids(byte[] bytes) {
        int bidCount = bytes[0] & 0xFF;
        List<Bid> result = new ArrayList<Bid>(bidCount);
        if (bidCount == 0) {
            return result;
        }

        int bitCount = 0;
        int buffer = 0;
        for (int i = 1; i < bytes.length; i++) {
            buffer |= (bytes[i] & 0xFF) << bitCount;
            bitCount += 8;
            while (bitCount >= 7 || i == bytes.length - 1) {
                if (bitCount <= 0 && i == bytes.length - 1) {
                    throw new RuntimeException("Could not deserialize bytes to list of bids");
                }

                if ((buffer & 1) == 0) {
                    // Special bid
                    buffer = buffer >>> 1;
                    result.add(SpecialBid.get(buffer & 3)); //00000011
                    buffer = buffer >>> 2;
                    bitCount -= 3;
                } else {
                    // Contract bid
                    buffer = buffer >>> 1;
                    result.add(ContractBid.get(buffer & 0x3F)); //00111111
                    buffer = buffer >>> 6;
                    bitCount -= 7;
                }

                if (bidCount == result.size()) {
                    return result;
                }
            }
        }

        return result;
    }

    private SerializationHelper() {
    }
}
